mirror of
https://github.com/ppy/osu.git
synced 2024-12-05 10:33:22 +08:00
Merge branch 'master' into slider_fix_lazyjump
This commit is contained in:
commit
60efb5ef92
@ -26,7 +26,7 @@
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="System.IO.Packaging" Version="8.0.1" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageReference Include="Velopack" Version="0.0.630-g9c52e40" />
|
||||
<PackageReference Include="Velopack" Version="0.0.869" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
<EmbeddedResource Include="lazer.ico" />
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
@ -73,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||
// 0.0 +--------+-+---------------> Release Difference / ms
|
||||
// release_threshold
|
||||
if (isOverlapping)
|
||||
holdAddition = 1 / (1 + Math.Exp(0.27 * (release_threshold - closestEndTime)));
|
||||
holdAddition = DifficultyCalculationUtils.Logistic(x: closestEndTime, multiplier: 0.27, midpointOffset: release_threshold);
|
||||
|
||||
// Decay and increase individualStrains in own column
|
||||
individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base);
|
||||
|
@ -54,7 +54,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
},
|
||||
columnBackgrounds = new ColumnFlow<Drawable>(stageDefinition)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Masking = false,
|
||||
},
|
||||
new HitTargetInsetContainer
|
||||
{
|
||||
@ -126,8 +127,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
},
|
||||
new Container
|
||||
{
|
||||
X = isLastColumn ? -0.16f : 0,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = rightLineWidth,
|
||||
Scale = new Vector2(0.740f, 1),
|
||||
|
@ -28,6 +28,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
private readonly FillFlowContainer<Container<TContent>> columns;
|
||||
private readonly StageDefinition stageDefinition;
|
||||
|
||||
public new bool Masking
|
||||
{
|
||||
get => base.Masking;
|
||||
set => base.Masking = value;
|
||||
}
|
||||
|
||||
public ColumnFlow(StageDefinition stageDefinition)
|
||||
{
|
||||
this.stageDefinition = stageDefinition;
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||
@ -24,38 +25,57 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
[Test]
|
||||
public void TestTouchInputAfterTouchingComposeArea()
|
||||
public void TestTouchInputPlaceHitCircleDirectly()
|
||||
{
|
||||
AddStep("tap circle", () => tap(this.ChildrenOfType<EditorRadioButton>().Single(b => b.Button.Label == "HitCircle")));
|
||||
|
||||
// this input is just for interacting with compose area
|
||||
AddStep("tap playfield", () => tap(this.ChildrenOfType<Playfield>().Single()));
|
||||
|
||||
AddStep("move current time", () => InputManager.Key(Key.Right));
|
||||
|
||||
AddStep("tap to place circle", () => tap(this.ChildrenOfType<Playfield>().Single().ToScreenSpace(new Vector2(10, 10))));
|
||||
AddStep("tap to place circle", () => tap(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddAssert("circle placed correctly", () =>
|
||||
{
|
||||
var circle = (HitCircle)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(circle.Position.X, Is.EqualTo(10f).Within(0.01f));
|
||||
Assert.That(circle.Position.Y, Is.EqualTo(10f).Within(0.01f));
|
||||
Assert.That(circle.Position.X, Is.EqualTo(256f).Within(0.01f));
|
||||
Assert.That(circle.Position.Y, Is.EqualTo(192f).Within(0.01f));
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
AddStep("tap slider", () => tap(this.ChildrenOfType<EditorRadioButton>().Single(b => b.Button.Label == "Slider")));
|
||||
[Test]
|
||||
public void TestTouchInputPlaceCircleAfterTouchingComposeArea()
|
||||
{
|
||||
AddStep("tap circle", () => tap(this.ChildrenOfType<EditorRadioButton>().Single(b => b.Button.Label == "HitCircle")));
|
||||
|
||||
// this input is just for interacting with compose area
|
||||
AddStep("tap playfield", () => tap(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddAssert("circle placed", () => EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate) is HitCircle);
|
||||
|
||||
AddStep("move current time", () => InputManager.Key(Key.Right));
|
||||
AddStep("move forward", () => InputManager.Key(Key.Right));
|
||||
|
||||
AddStep("tap to place circle", () => tap(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddAssert("circle placed correctly", () =>
|
||||
{
|
||||
var circle = (HitCircle)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(circle.Position.X, Is.EqualTo(256f).Within(0.01f));
|
||||
Assert.That(circle.Position.Y, Is.EqualTo(192f).Within(0.01f));
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTouchInputPlaceSliderDirectly()
|
||||
{
|
||||
AddStep("tap slider", () => tap(this.ChildrenOfType<EditorRadioButton>().Single(b => b.Button.Label == "Slider")));
|
||||
|
||||
AddStep("hold to draw slider", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, this.ChildrenOfType<Playfield>().Single().ToScreenSpace(new Vector2(50, 20)))));
|
||||
AddStep("drag to draw", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, this.ChildrenOfType<Playfield>().Single().ToScreenSpace(new Vector2(200, 50)))));
|
||||
AddAssert("selection not initiated", () => this.ChildrenOfType<DragBox>().All(d => d.State == Visibility.Hidden));
|
||||
AddAssert("blueprint visible", () => this.ChildrenOfType<SliderPlacementBlueprint>().Single().Alpha > 0);
|
||||
AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value)));
|
||||
AddAssert("slider placed correctly", () =>
|
||||
{
|
||||
@ -76,12 +96,55 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
}
|
||||
|
||||
private void tap(Drawable drawable) => tap(drawable.ScreenSpaceDrawQuad.Centre);
|
||||
[Test]
|
||||
public void TestTouchInputPlaceSliderAfterTouchingComposeArea()
|
||||
{
|
||||
AddStep("tap slider", () => tap(this.ChildrenOfType<EditorRadioButton>().Single(b => b.Button.Label == "Slider")));
|
||||
|
||||
AddStep("tap playfield", () => tap(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("tap and hold another spot", () => hold(this.ChildrenOfType<Playfield>().Single(), new Vector2(50, 0)));
|
||||
AddUntilStep("wait for slider placement", () => EditorBeatmap.HitObjects.SingleOrDefault(h => h.StartTime == EditorClock.CurrentTimeAccurate) is Slider);
|
||||
AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value)));
|
||||
|
||||
AddStep("move forward", () => InputManager.Key(Key.Right));
|
||||
|
||||
AddStep("hold to draw slider", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, this.ChildrenOfType<Playfield>().Single().ToScreenSpace(new Vector2(50, 20)))));
|
||||
AddStep("drag to draw", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, this.ChildrenOfType<Playfield>().Single().ToScreenSpace(new Vector2(200, 50)))));
|
||||
AddAssert("selection not initiated", () => this.ChildrenOfType<DragBox>().All(d => d.State == Visibility.Hidden));
|
||||
AddAssert("blueprint visible", () => this.ChildrenOfType<SliderPlacementBlueprint>().Single().IsPresent);
|
||||
AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value)));
|
||||
AddAssert("slider placed correctly", () =>
|
||||
{
|
||||
var slider = (Slider)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(slider.Position.X, Is.EqualTo(50f).Within(0.01f));
|
||||
Assert.That(slider.Position.Y, Is.EqualTo(20f).Within(0.01f));
|
||||
Assert.That(slider.Path.ControlPoints.Count, Is.EqualTo(2));
|
||||
Assert.That(slider.Path.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
|
||||
|
||||
// the final position may be slightly off from the mouse position when drawing, account for that.
|
||||
Assert.That(slider.Path.ControlPoints[1].Position.X, Is.EqualTo(150).Within(5));
|
||||
Assert.That(slider.Path.ControlPoints[1].Position.Y, Is.EqualTo(30).Within(5));
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void tap(Drawable drawable, Vector2 offset = default) => tap(drawable.ToScreenSpace(drawable.LayoutRectangle.Centre + offset));
|
||||
|
||||
private void tap(Vector2 position)
|
||||
{
|
||||
InputManager.BeginTouch(new Touch(TouchSource.Touch1, position));
|
||||
hold(position);
|
||||
InputManager.EndTouch(new Touch(TouchSource.Touch1, position));
|
||||
}
|
||||
|
||||
private void hold(Drawable drawable, Vector2 offset = default) => hold(drawable.ToScreenSpace(drawable.LayoutRectangle.Centre + offset));
|
||||
|
||||
private void hold(Vector2 position)
|
||||
{
|
||||
InputManager.BeginTouch(new Touch(TouchSource.Touch1, position));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
var osuLastObj = (OsuDifficultyHitObject)current.Previous(0);
|
||||
var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
|
||||
|
||||
const int radius = OsuDifficultyHitObject.NORMALISED_RADIUS;
|
||||
const int diameter = OsuDifficultyHitObject.NORMALISED_DIAMETER;
|
||||
|
||||
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
|
||||
double currDistance = withSliderTravelDistance ? osuCurrObj.LazyJumpFromEndDistance : osuCurrObj.JumpDistance;
|
||||
double currVelocity = currDistance / osuCurrObj.StrainTime;
|
||||
@ -79,14 +83,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
wideAngleBonus = calcWideAngleBonus(currAngle);
|
||||
acuteAngleBonus = calcAcuteAngleBonus(currAngle);
|
||||
|
||||
if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2.
|
||||
if (DifficultyCalculationUtils.MillisecondsToBPM(osuCurrObj.StrainTime, 2) < 300) // Only buff deltaTime exceeding 300 bpm 1/2.
|
||||
acuteAngleBonus = 0;
|
||||
else
|
||||
{
|
||||
acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
|
||||
* Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime
|
||||
* Math.Min(angleBonus, diameter * 1.25 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(currDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter).
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(currDistance, radius, diameter) - radius) / radius), 2); // Buff distance exceeding radius up to diameter.
|
||||
}
|
||||
|
||||
// Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
|
||||
@ -109,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2);
|
||||
|
||||
// Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
|
||||
double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity));
|
||||
double overlapVelocityBuff = Math.Min(diameter * 1.25 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity));
|
||||
|
||||
velocityChangeBonus = overlapVelocityBuff * distRatio;
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
@ -120,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
islandCount.Count++;
|
||||
|
||||
// repeated island (ex: triplet -> triplet)
|
||||
double power = logistic(island.Delta, 2.75, 0.24, 14);
|
||||
double power = DifficultyCalculationUtils.Logistic(island.Delta, maxValue: 2.75, multiplier: 0.24, midpointOffset: 58.33);
|
||||
effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power));
|
||||
|
||||
islandCounts[countIndex] = (islandCount.Island, islandCount.Count);
|
||||
@ -172,8 +173,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_overall_multiplier) / 2.0; // produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
||||
}
|
||||
|
||||
private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x))));
|
||||
|
||||
private class Island : IEquatable<Island>
|
||||
{
|
||||
private readonly double deltaDifferenceEpsilon;
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
@ -10,8 +11,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
{
|
||||
public static class SpeedEvaluator
|
||||
{
|
||||
private const double single_spacing_threshold = 125; // 1.25 circles distance between centers
|
||||
private const double min_speed_bonus = 75; // ~200BPM
|
||||
private const double single_spacing_threshold = OsuDifficultyHitObject.NORMALISED_DIAMETER * 1.25; // 1.25 circles distance between centers
|
||||
private const double min_speed_bonus = 200; // 200 BPM 1/4th
|
||||
private const double speed_balancing_factor = 40;
|
||||
private const double distance_multiplier = 0.94;
|
||||
|
||||
@ -43,8 +44,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
double speedBonus = 0.0;
|
||||
|
||||
// Add additional scaling bonus for streams/bursts higher than 200bpm
|
||||
if (strainTime < min_speed_bonus)
|
||||
speedBonus = 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
|
||||
if (DifficultyCalculationUtils.MillisecondsToBPM(strainTime) > min_speed_bonus)
|
||||
speedBonus = 0.75 * Math.Pow((DifficultyCalculationUtils.BPMToMilliseconds(min_speed_bonus) - strainTime) / speed_balancing_factor, 2);
|
||||
|
||||
double travelDistance = osuPrevObj?.TravelDistance ?? 0;
|
||||
double distance = travelDistance + osuCurrObj.MinimumJumpDistance;
|
||||
|
@ -48,8 +48,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
double sliderFactor = aimRating > 0 ? Math.Min(aimRating, aimRatingNoSliders) / aimRating : 1;
|
||||
|
||||
double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains();
|
||||
double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains();
|
||||
double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountTopWeightedStrains();
|
||||
double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountTopWeightedStrains();
|
||||
|
||||
if (mods.Any(m => m is OsuModTouchDevice))
|
||||
{
|
||||
|
@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
/// </summary>
|
||||
public const int NORMALISED_RADIUS = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||
|
||||
public const int NORMALISED_DIAMETER = NORMALISED_RADIUS * 2;
|
||||
|
||||
public const int MIN_DELTA_TIME = 25;
|
||||
|
||||
private const float maximum_slider_radius = NORMALISED_RADIUS * 2.4f;
|
||||
|
@ -34,7 +34,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
currentStrain *= strainDecay(current.DeltaTime);
|
||||
currentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * skillMultiplier;
|
||||
ObjectStrains.Add(currentStrain);
|
||||
|
||||
return currentStrain;
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// </summary>
|
||||
protected virtual double ReducedStrainBaseline => 0.75;
|
||||
|
||||
protected List<double> ObjectStrains = new List<double>();
|
||||
protected double Difficulty;
|
||||
|
||||
protected OsuStrainSkill(Mod[] mods)
|
||||
@ -60,20 +59,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
return Difficulty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of strains weighted against the top strain.
|
||||
/// The result is scaled by clock rate as it affects the total number of strains.
|
||||
/// </summary>
|
||||
public double CountDifficultStrains()
|
||||
{
|
||||
if (Difficulty == 0)
|
||||
return 0.0;
|
||||
|
||||
double consistentTopStrain = Difficulty / 10; // What would the top strain be if all strain values were identical
|
||||
// Use a weighted sum of all strains. Constants are arbitrary and give nice values
|
||||
return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88))));
|
||||
}
|
||||
|
||||
public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
|
||||
|
||||
double totalStrain = currentStrain * currentRhythm;
|
||||
ObjectStrains.Add(totalStrain);
|
||||
|
||||
return totalStrain;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osuTK;
|
||||
@ -31,12 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
public override void EndPlacement(bool commit)
|
||||
{
|
||||
if (!commit && PlacementActive != PlacementState.Finished)
|
||||
{
|
||||
gridToolboxGroup.StartPosition.Value = originalOrigin;
|
||||
gridToolboxGroup.Spacing.Value = originalSpacing;
|
||||
if (!gridToolboxGroup.GridLinesRotation.Disabled)
|
||||
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
|
||||
}
|
||||
resetGridState();
|
||||
|
||||
base.EndPlacement(commit);
|
||||
|
||||
@ -103,6 +99,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
|
||||
public override void UpdateTimeAndPosition(SnapResult result)
|
||||
{
|
||||
if (State.Value == Visibility.Hidden)
|
||||
return;
|
||||
|
||||
var pos = ToLocalSpace(result.ScreenSpacePosition);
|
||||
|
||||
if (PlacementActive != PlacementState.Active)
|
||||
@ -122,5 +121,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
base.PopOut();
|
||||
resetGridState();
|
||||
}
|
||||
|
||||
private void resetGridState()
|
||||
{
|
||||
gridToolboxGroup.StartPosition.Value = originalOrigin;
|
||||
gridToolboxGroup.Spacing.Value = originalSpacing;
|
||||
if (!gridToolboxGroup.GridLinesRotation.Disabled)
|
||||
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +156,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
return true;
|
||||
}
|
||||
|
||||
// this allows sliders to be drawn outside compose area (after starting from a point within the compose area).
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || PlacementActive == PlacementState.Active;
|
||||
|
||||
// ReceivePositionalInputAtSubTree generally always returns true when masking is disabled, but we don't want that,
|
||||
// otherwise a slider path tooltip will be displayed anywhere in the editor (outside compose area).
|
||||
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
private void beginNewSegment(PathControlPoint lastPoint)
|
||||
{
|
||||
segmentStart = lastPoint;
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour.Data;
|
||||
@ -11,26 +12,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
||||
{
|
||||
public class ColourEvaluator
|
||||
{
|
||||
/// <summary>
|
||||
/// A sigmoid function. It gives a value between (middle - height/2) and (middle + height/2).
|
||||
/// </summary>
|
||||
/// <param name="val">The input value.</param>
|
||||
/// <param name="center">The center of the sigmoid, where the largest gradient occurs and value is equal to middle.</param>
|
||||
/// <param name="width">The radius of the sigmoid, outside of which values are near the minimum/maximum.</param>
|
||||
/// <param name="middle">The middle of the sigmoid output.</param>
|
||||
/// <param name="height">The height of the sigmoid output. This will be equal to max value - min value.</param>
|
||||
private static double sigmoid(double val, double center, double width, double middle, double height)
|
||||
{
|
||||
double sigmoid = Math.Tanh(Math.E * -(val - center) / width);
|
||||
return sigmoid * (height / 2) + middle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate the difficulty of the first note of a <see cref="MonoStreak"/>.
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(MonoStreak monoStreak)
|
||||
{
|
||||
return sigmoid(monoStreak.Index, 2, 2, 0.5, 1) * EvaluateDifficultyOf(monoStreak.Parent) * 0.5;
|
||||
return DifficultyCalculationUtils.Logistic(exponent: Math.E * monoStreak.Index - 2 * Math.E) * EvaluateDifficultyOf(monoStreak.Parent) * 0.5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -38,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(AlternatingMonoPattern alternatingMonoPattern)
|
||||
{
|
||||
return sigmoid(alternatingMonoPattern.Index, 2, 2, 0.5, 1) * EvaluateDifficultyOf(alternatingMonoPattern.Parent);
|
||||
return DifficultyCalculationUtils.Logistic(exponent: Math.E * alternatingMonoPattern.Index - 2 * Math.E) * EvaluateDifficultyOf(alternatingMonoPattern.Parent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -46,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(RepeatingHitPatterns repeatingHitPattern)
|
||||
{
|
||||
return 2 * (1 - sigmoid(repeatingHitPattern.RepetitionInterval, 2, 2, 0.5, 1));
|
||||
return 2 * (1 - DifficultyCalculationUtils.Logistic(exponent: Math.E * repeatingHitPattern.RepetitionInterval - 2 * Math.E));
|
||||
}
|
||||
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject hitObject)
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
Add(metadataClient);
|
||||
|
||||
// add button to observe for daily challenge changes and perform its logic.
|
||||
Add(new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D));
|
||||
Add(new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), (_, _) => { }, 0, Key.D));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -19,6 +19,7 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
@ -82,6 +83,45 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlacementOutsideComposeScreen()
|
||||
{
|
||||
AddStep("clear all control points and hitobjects", () =>
|
||||
{
|
||||
editorBeatmap.ControlPointInfo.Clear();
|
||||
editorBeatmap.Clear();
|
||||
});
|
||||
|
||||
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||
AddStep("select circle", () => hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").TriggerClick());
|
||||
AddStep("move mouse to compose", () => InputManager.MoveMouseTo(hitObjectComposer.ChildrenOfType<HitObjectContainer>().Single()));
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("circle placed", () => editorBeatmap.HitObjects.Count == 1);
|
||||
|
||||
AddStep("move mouse outside compose", () => InputManager.MoveMouseTo(hitObjectComposer.ChildrenOfType<HitObjectContainer>().Single().ScreenSpaceDrawQuad.TopLeft - new Vector2(0f, 20f)));
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("no circle placed", () => editorBeatmap.HitObjects.Count == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragSliderOutsideComposeScreen()
|
||||
{
|
||||
AddStep("clear all control points and hitobjects", () =>
|
||||
{
|
||||
editorBeatmap.ControlPointInfo.Clear();
|
||||
editorBeatmap.Clear();
|
||||
});
|
||||
|
||||
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||
AddStep("select slider", () => hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "Slider").TriggerClick());
|
||||
|
||||
AddStep("move mouse to compose", () => InputManager.MoveMouseTo(hitObjectComposer.ChildrenOfType<HitObjectContainer>().Single()));
|
||||
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddStep("move mouse outside compose", () => InputManager.MoveMouseTo(hitObjectComposer.ChildrenOfType<HitObjectContainer>().Single().ScreenSpaceDrawQuad.TopLeft - new Vector2(0f, 80f)));
|
||||
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("slider placed", () => editorBeatmap.HitObjects.Count == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlacementOnlyWorksWithTiming()
|
||||
{
|
||||
|
@ -55,21 +55,21 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Username = @"flyte",
|
||||
Id = 3103765,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
|
||||
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
||||
}),
|
||||
new UserBrickPanel(new APIUser
|
||||
{
|
||||
Username = @"peppy",
|
||||
Id = 2,
|
||||
Colour = "99EB47",
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||
}),
|
||||
new UserGridPanel(new APIUser
|
||||
{
|
||||
Username = @"flyte",
|
||||
Id = 3103765,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg",
|
||||
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
||||
Status = { Value = UserStatus.Online }
|
||||
}) { Width = 300 },
|
||||
boundPanel1 = new UserGridPanel(new APIUser
|
||||
@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Username = @"peppy",
|
||||
Id = 2,
|
||||
CountryCode = CountryCode.AU,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||
IsSupporter = true,
|
||||
SupportLevel = 3,
|
||||
}) { Width = 300 },
|
||||
@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Username = @"flyte",
|
||||
Id = 3103765,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg",
|
||||
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
||||
Statistics = new UserStatistics { GlobalRank = 12345, CountryRank = 1234 }
|
||||
}) { Width = 300 },
|
||||
new UserRankPanel(new APIUser
|
||||
@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = 2,
|
||||
Colour = "99EB47",
|
||||
CountryCode = CountryCode.AU,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||
Statistics = new UserStatistics { GlobalRank = null, CountryRank = null }
|
||||
}) { Width = 300 }
|
||||
}
|
||||
@ -168,6 +168,14 @@ namespace osu.Game.Tests.Visual.Online
|
||||
CountryRank = RNG.Next(100000)
|
||||
});
|
||||
});
|
||||
AddStep("set statistics to something big", () =>
|
||||
{
|
||||
API.UpdateStatistics(new UserStatistics
|
||||
{
|
||||
GlobalRank = RNG.Next(1_000_000, 100_000_000),
|
||||
CountryRank = RNG.Next(1_000_000, 100_000_000)
|
||||
});
|
||||
});
|
||||
AddStep("set statistics to empty", () =>
|
||||
{
|
||||
API.UpdateStatistics(new UserStatistics());
|
||||
|
@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
break;
|
||||
|
||||
case Key.Q:
|
||||
buttons.OnExit = action;
|
||||
buttons.OnExit = _ => action();
|
||||
break;
|
||||
|
||||
case Key.O:
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
public void TestStandardButton()
|
||||
{
|
||||
AddStep("add button", () => Child = new MainMenuButton(
|
||||
ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), _ => { }, 0, Key.P)
|
||||
ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), (_, _) => { }, 0, Key.P)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)],
|
||||
Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)
|
||||
Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), (_, _) => { }, 0, Key.D)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -161,7 +161,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)],
|
||||
Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)
|
||||
Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), (_, _) => { }, 0, Key.D)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -26,10 +26,10 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
protected virtual int SectionLength => 400;
|
||||
|
||||
private double currentSectionPeak; // We also keep track of the peak strain level in the current section.
|
||||
|
||||
private double currentSectionEnd;
|
||||
|
||||
private readonly List<double> strainPeaks = new List<double>();
|
||||
protected readonly List<double> ObjectStrains = new List<double>(); // Store individual strains
|
||||
|
||||
protected StrainSkill(Mod[] mods)
|
||||
: base(mods)
|
||||
@ -57,7 +57,25 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
currentSectionEnd += SectionLength;
|
||||
}
|
||||
|
||||
currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak);
|
||||
double strain = StrainValueAt(current);
|
||||
currentSectionPeak = Math.Max(strain, currentSectionPeak);
|
||||
|
||||
// Store the strain value for the object
|
||||
ObjectStrains.Add(strain);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the number of strains weighted against the top strain.
|
||||
/// The result is scaled by clock rate as it affects the total number of strains.
|
||||
/// </summary>
|
||||
public virtual double CountTopWeightedStrains()
|
||||
{
|
||||
if (ObjectStrains.Count == 0)
|
||||
return 0.0;
|
||||
|
||||
double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical
|
||||
// Use a weighted sum of all strains. Constants are arbitrary and give nice values
|
||||
return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88))));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -0,0 +1,50 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty.Utils
|
||||
{
|
||||
public static class DifficultyCalculationUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts BPM value into milliseconds
|
||||
/// </summary>
|
||||
/// <param name="bpm">Beats per minute</param>
|
||||
/// <param name="delimiter">Which rhythm delimiter to use, default is 1/4</param>
|
||||
/// <returns>BPM conveted to milliseconds</returns>
|
||||
public static double BPMToMilliseconds(double bpm, int delimiter = 4)
|
||||
{
|
||||
return 60000.0 / delimiter / bpm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts milliseconds value into a BPM value
|
||||
/// </summary>
|
||||
/// <param name="ms">Milliseconds</param>
|
||||
/// <param name="delimiter">Which rhythm delimiter to use, default is 1/4</param>
|
||||
/// <returns>Milliseconds conveted to beats per minute</returns>
|
||||
public static double MillisecondsToBPM(double ms, int delimiter = 4)
|
||||
{
|
||||
return 60000.0 / (ms * delimiter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a S-shaped logistic function (https://en.wikipedia.org/wiki/Logistic_function)
|
||||
/// </summary>
|
||||
/// <param name="x">Value to calculate the function for</param>
|
||||
/// <param name="maxValue">Maximum value returnable by the function</param>
|
||||
/// <param name="multiplier">Growth rate of the function</param>
|
||||
/// <param name="midpointOffset">How much the function midpoint is offset from zero <paramref name="x"/></param>
|
||||
/// <returns>The output of logistic function of <paramref name="x"/></returns>
|
||||
public static double Logistic(double x, double midpointOffset, double multiplier, double maxValue = 1) => maxValue / (1 + Math.Exp(multiplier * (midpointOffset - x)));
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a S-shaped logistic function (https://en.wikipedia.org/wiki/Logistic_function)
|
||||
/// </summary>
|
||||
/// <param name="maxValue">Maximum value returnable by the function</param>
|
||||
/// <param name="exponent">Exponent</param>
|
||||
/// <returns>The output of logistic function</returns>
|
||||
public static double Logistic(double exponent, double maxValue = 1) => maxValue / (1 + Math.Exp(exponent));
|
||||
}
|
||||
}
|
@ -537,23 +537,24 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
#region IPlacementHandler
|
||||
|
||||
public void BeginPlacement(HitObject hitObject)
|
||||
public void ShowPlacement(HitObject hitObject)
|
||||
{
|
||||
EditorBeatmap.PlacementObject.Value = hitObject;
|
||||
}
|
||||
|
||||
public void EndPlacement(HitObject hitObject, bool commit)
|
||||
public void HidePlacement()
|
||||
{
|
||||
EditorBeatmap.PlacementObject.Value = null;
|
||||
}
|
||||
|
||||
if (commit)
|
||||
public void CommitPlacement(HitObject hitObject)
|
||||
{
|
||||
EditorBeatmap.PlacementObject.Value = null;
|
||||
EditorBeatmap.Add(hitObject);
|
||||
|
||||
if (autoSeekOnPlacement.Value && EditorClock.CurrentTime < hitObject.StartTime)
|
||||
EditorClock.SeekSmoothlyTo(hitObject.StartTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject);
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -63,18 +64,26 @@ namespace osu.Game.Rulesets.Edit
|
||||
startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true);
|
||||
}
|
||||
|
||||
private bool placementBegun;
|
||||
|
||||
protected override void BeginPlacement(bool commitStart = false)
|
||||
{
|
||||
base.BeginPlacement(commitStart);
|
||||
|
||||
placementHandler.BeginPlacement(HitObject);
|
||||
if (State.Value == Visibility.Visible)
|
||||
placementHandler.ShowPlacement(HitObject);
|
||||
|
||||
placementBegun = true;
|
||||
}
|
||||
|
||||
public override void EndPlacement(bool commit)
|
||||
{
|
||||
base.EndPlacement(commit);
|
||||
|
||||
placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit);
|
||||
if (IsValidForPlacement && commit)
|
||||
placementHandler.CommitPlacement(HitObject);
|
||||
else
|
||||
placementHandler.HidePlacement();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -127,5 +136,19 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// refreshing <see cref="Objects.HitObject.NestedHitObjects"/> and parameters for the <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
base.PopIn();
|
||||
|
||||
if (placementBegun)
|
||||
placementHandler.ShowPlacement(HitObject);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
base.PopOut();
|
||||
placementHandler.HidePlacement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
@ -15,7 +14,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// <summary>
|
||||
/// A blueprint which governs the placement of something.
|
||||
/// </summary>
|
||||
public abstract partial class PlacementBlueprint : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
||||
public abstract partial class PlacementBlueprint : VisibilityContainer, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the <see cref="HitObject"/> is currently mid-placement, but has not necessarily finished being placed.
|
||||
@ -31,12 +30,17 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// </remarks>
|
||||
protected virtual bool IsValidForPlacement => true;
|
||||
|
||||
// the blueprint should still be considered for input even if it is hidden,
|
||||
// especially when such input is the reason for making the blueprint become visible.
|
||||
public override bool PropagatePositionalInputSubTree => true;
|
||||
public override bool PropagateNonPositionalInputSubTree => true;
|
||||
|
||||
protected PlacementBlueprint()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
// This is required to allow the blueprint's position to be updated via OnMouseMove/Handle
|
||||
// on the same frame it is made visible via a PlacementState change.
|
||||
// the blueprint should still be considered for input even if it is hidden,
|
||||
// especially when such input is the reason for making the blueprint become visible.
|
||||
AlwaysPresent = true;
|
||||
}
|
||||
|
||||
@ -104,8 +108,6 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
protected override bool Handle(UIEvent e)
|
||||
{
|
||||
base.Handle(e);
|
||||
@ -127,6 +129,9 @@ namespace osu.Game.Rulesets.Edit
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PopIn() => this.FadeIn();
|
||||
protected override void PopOut() => this.FadeOut();
|
||||
|
||||
public enum PlacementState
|
||||
{
|
||||
Waiting,
|
||||
|
@ -316,9 +316,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// <summary>
|
||||
/// Refreshes the current placement tool.
|
||||
/// </summary>
|
||||
private void refreshTool()
|
||||
private void refreshPlacement()
|
||||
{
|
||||
removePlacement();
|
||||
CurrentPlacement?.EndPlacement(false);
|
||||
CurrentPlacement?.Expire();
|
||||
CurrentPlacement = null;
|
||||
|
||||
ensurePlacementCreated();
|
||||
}
|
||||
|
||||
@ -344,22 +347,25 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
case PlacementBlueprint.PlacementState.Waiting:
|
||||
if (!Composer.CursorInPlacementArea)
|
||||
removePlacement();
|
||||
CurrentPlacement.Hide();
|
||||
else
|
||||
CurrentPlacement.Show();
|
||||
|
||||
break;
|
||||
|
||||
case PlacementBlueprint.PlacementState.Active:
|
||||
CurrentPlacement.Show();
|
||||
break;
|
||||
|
||||
case PlacementBlueprint.PlacementState.Finished:
|
||||
removePlacement();
|
||||
refreshPlacement();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Composer.CursorInPlacementArea)
|
||||
ensurePlacementCreated();
|
||||
|
||||
// updates the placement with the latest editor clock time.
|
||||
if (CurrentPlacement != null)
|
||||
updatePlacementTimeAndPosition();
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
@ -386,7 +392,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
private void hitObjectAdded(HitObject obj)
|
||||
{
|
||||
// refresh the tool to handle the case of placement completing.
|
||||
refreshTool();
|
||||
refreshPlacement();
|
||||
|
||||
// on successful placement, the new combo button should be reset as this is the most common user interaction.
|
||||
if (Beatmap.SelectedHitObjects.Count == 0)
|
||||
@ -415,14 +421,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
public void CommitIfPlacementActive()
|
||||
{
|
||||
CurrentPlacement?.EndPlacement(CurrentPlacement.PlacementActive == PlacementBlueprint.PlacementState.Active);
|
||||
removePlacement();
|
||||
}
|
||||
|
||||
private void removePlacement()
|
||||
{
|
||||
CurrentPlacement?.EndPlacement(false);
|
||||
CurrentPlacement?.Expire();
|
||||
CurrentPlacement = null;
|
||||
refreshPlacement();
|
||||
}
|
||||
|
||||
private CompositionTool currentTool;
|
||||
|
@ -10,17 +10,21 @@ namespace osu.Game.Screens.Edit.Compose
|
||||
public interface IPlacementHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Notifies that a placement has begun.
|
||||
/// Notifies that a placement blueprint became visible on the screen.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> being placed.</param>
|
||||
void BeginPlacement(HitObject hitObject);
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> representing the placement.</param>
|
||||
void ShowPlacement(HitObject hitObject);
|
||||
|
||||
/// <summary>
|
||||
/// Notifies that a placement has finished.
|
||||
/// Notifies that a visible placement blueprint has been hidden.
|
||||
/// </summary>
|
||||
void HidePlacement();
|
||||
|
||||
/// <summary>
|
||||
/// Notifies that a placement has been committed.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> that has been placed.</param>
|
||||
/// <param name="commit">Whether the object should be committed.</param>
|
||||
void EndPlacement(HitObject hitObject, bool commit);
|
||||
void CommitPlacement(HitObject hitObject);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a <see cref="HitObject"/>.
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
public Action? OnEditBeatmap;
|
||||
public Action? OnEditSkin;
|
||||
public Action? OnExit;
|
||||
public Action<UIEvent>? OnExit;
|
||||
public Action? OnBeatmapListing;
|
||||
public Action? OnSolo;
|
||||
public Action? OnSettings;
|
||||
@ -104,11 +104,11 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
buttonArea.AddRange(new Drawable[]
|
||||
{
|
||||
new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), _ => OnSettings?.Invoke(), Key.O, Key.S)
|
||||
new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), (_, _) => OnSettings?.Invoke(), Key.O, Key.S)
|
||||
{
|
||||
Padding = new MarginPadding { Right = WEDGE_WIDTH },
|
||||
},
|
||||
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), _ => State = ButtonSystemState.TopLevel)
|
||||
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), (_, _) => State = ButtonSystemState.TopLevel)
|
||||
{
|
||||
Padding = new MarginPadding { Right = WEDGE_WIDTH },
|
||||
VisibleStateMin = ButtonSystemState.Play,
|
||||
@ -132,7 +132,7 @@ namespace osu.Game.Screens.Menu
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, IdleTracker? idleTracker, GameHost host)
|
||||
{
|
||||
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), _ => OnSolo?.Invoke(), Key.P)
|
||||
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), (_, _) => OnSolo?.Invoke(), Key.P)
|
||||
{
|
||||
Padding = new MarginPadding { Left = WEDGE_WIDTH },
|
||||
});
|
||||
@ -141,22 +141,22 @@ namespace osu.Game.Screens.Menu
|
||||
buttonsPlay.Add(new DailyChallengeButton(@"button-daily-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D));
|
||||
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
||||
|
||||
buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), _ => OnEditBeatmap?.Invoke(), Key.B, Key.E)
|
||||
buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), (_, _) => OnEditBeatmap?.Invoke(), Key.B, Key.E)
|
||||
{
|
||||
Padding = new MarginPadding { Left = WEDGE_WIDTH },
|
||||
});
|
||||
buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), _ => OnEditSkin?.Invoke(), Key.S));
|
||||
buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), (_, _) => OnEditSkin?.Invoke(), Key.S));
|
||||
buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit);
|
||||
|
||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), _ => State = ButtonSystemState.Play, Key.P, Key.M, Key.L)
|
||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), (_, _) => State = ButtonSystemState.Play, Key.P, Key.M, Key.L)
|
||||
{
|
||||
Padding = new MarginPadding { Left = WEDGE_WIDTH },
|
||||
});
|
||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), _ => State = ButtonSystemState.Edit, Key.E));
|
||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), _ => OnBeatmapListing?.Invoke(), Key.B, Key.D));
|
||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), (_, _) => State = ButtonSystemState.Edit, Key.E));
|
||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), (_, _) => OnBeatmapListing?.Invoke(), Key.B, Key.D));
|
||||
|
||||
if (host.CanExit)
|
||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), _ => OnExit?.Invoke(), Key.Q));
|
||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), (_, e) => OnExit?.Invoke(e), Key.Q));
|
||||
|
||||
buttonArea.AddRange(buttonsPlay);
|
||||
buttonArea.AddRange(buttonsEdit);
|
||||
@ -179,7 +179,7 @@ namespace osu.Game.Screens.Menu
|
||||
sampleLogoSwoosh = audio.Samples.Get(@"Menu/osu-logo-swoosh");
|
||||
}
|
||||
|
||||
private void onMultiplayer(MainMenuButton _)
|
||||
private void onMultiplayer(MainMenuButton mainMenuButton, UIEvent uiEvent)
|
||||
{
|
||||
if (api.State.Value != APIState.Online)
|
||||
{
|
||||
@ -190,7 +190,7 @@ namespace osu.Game.Screens.Menu
|
||||
OnMultiplayer?.Invoke();
|
||||
}
|
||||
|
||||
private void onPlaylists(MainMenuButton _)
|
||||
private void onPlaylists(MainMenuButton mainMenuButton, UIEvent uiEvent)
|
||||
{
|
||||
if (api.State.Value != APIState.Online)
|
||||
{
|
||||
@ -201,7 +201,7 @@ namespace osu.Game.Screens.Menu
|
||||
OnPlaylists?.Invoke();
|
||||
}
|
||||
|
||||
private void onDailyChallenge(MainMenuButton button)
|
||||
private void onDailyChallenge(MainMenuButton button, UIEvent uiEvent)
|
||||
{
|
||||
if (api.State.Value != APIState.Online)
|
||||
{
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
@ -50,7 +51,7 @@ namespace osu.Game.Screens.Menu
|
||||
[Resolved]
|
||||
private SessionStatics statics { get; set; } = null!;
|
||||
|
||||
public DailyChallengeButton(string sampleName, Color4 colour, Action<MainMenuButton>? clickAction = null, params Key[] triggerKeys)
|
||||
public DailyChallengeButton(string sampleName, Color4 colour, Action<MainMenuButton, UIEvent>? clickAction = null, params Key[] triggerKeys)
|
||||
: base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys)
|
||||
{
|
||||
BaseSize = new Vector2(ButtonSystem.BUTTON_WIDTH * 1.3f, ButtonArea.BUTTON_AREA_HEIGHT);
|
||||
|
@ -153,9 +153,9 @@ namespace osu.Game.Screens.Menu
|
||||
else
|
||||
this.Push(new DailyChallengeIntro(room));
|
||||
},
|
||||
OnExit = () =>
|
||||
OnExit = e =>
|
||||
{
|
||||
exitConfirmedViaHoldOrClick = true;
|
||||
exitConfirmedViaHoldOrClick = e is MouseEvent;
|
||||
this.Exit();
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
protected Vector2 BaseSize { get; init; } = new Vector2(ButtonSystem.BUTTON_WIDTH, ButtonArea.BUTTON_AREA_HEIGHT);
|
||||
|
||||
private readonly Action<MainMenuButton>? clickAction;
|
||||
private readonly Action<MainMenuButton, UIEvent>? clickAction;
|
||||
|
||||
private readonly Container background;
|
||||
private readonly Drawable backgroundContent;
|
||||
@ -84,7 +84,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => background.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action<MainMenuButton>? clickAction = null, params Key[] triggerKeys)
|
||||
public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action<MainMenuButton, UIEvent>? clickAction = null, params Key[] triggerKeys)
|
||||
{
|
||||
this.sampleName = sampleName;
|
||||
this.clickAction = clickAction;
|
||||
@ -263,7 +263,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
trigger();
|
||||
trigger(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -274,19 +274,19 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
if (TriggerKeys.Contains(e.Key))
|
||||
{
|
||||
trigger();
|
||||
trigger(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void trigger()
|
||||
private void trigger(UIEvent e)
|
||||
{
|
||||
sampleChannel = sampleClick?.GetChannel();
|
||||
sampleChannel?.Play();
|
||||
|
||||
clickAction?.Invoke(this);
|
||||
clickAction?.Invoke(this, e);
|
||||
|
||||
boxHoverLayer.ClearTransforms();
|
||||
boxHoverLayer.Alpha = 0.9f;
|
||||
|
@ -224,7 +224,7 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
},
|
||||
},
|
||||
idleTracker = new IdleTracker(750),
|
||||
idleTracker = new IdleTracker(1500),
|
||||
sampleRestart = new SkinnableSound(new SampleInfo(@"Gameplay/restart", @"pause-retry-click"))
|
||||
};
|
||||
|
||||
|
@ -222,12 +222,6 @@ namespace osu.Game.Screens.Select
|
||||
InternalChild = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
// Avoid clash between scrollbar and osu! logo.
|
||||
Top = 10,
|
||||
Bottom = 100,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
setPool,
|
||||
@ -1266,6 +1260,38 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
return base.OnDragStart(e);
|
||||
}
|
||||
|
||||
protected override ScrollbarContainer CreateScrollbar(Direction direction)
|
||||
{
|
||||
return new PaddedScrollbar();
|
||||
}
|
||||
|
||||
protected partial class PaddedScrollbar : OsuScrollbar
|
||||
{
|
||||
public PaddedScrollbar()
|
||||
: base(Direction.Vertical)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private const float top_padding = 10;
|
||||
private const float bottom_padding = 70;
|
||||
|
||||
protected override float ToScrollbarPosition(float scrollPosition)
|
||||
{
|
||||
if (Precision.AlmostEquals(0, ScrollableExtent))
|
||||
return 0;
|
||||
|
||||
return top_padding + (ScrollbarMovementExtent - (top_padding + bottom_padding)) * (scrollPosition / ScrollableExtent);
|
||||
}
|
||||
|
||||
protected override float FromScrollbarPosition(float scrollbarPosition)
|
||||
{
|
||||
if (Precision.AlmostEquals(0, ScrollbarMovementExtent))
|
||||
return 0;
|
||||
|
||||
return ScrollableExtent * ((scrollbarPosition - top_padding) / (ScrollbarMovementExtent - (top_padding + bottom_padding)));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -59,20 +59,20 @@ namespace osu.Game.Tests.Visual
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ResetPlacement();
|
||||
}
|
||||
|
||||
public void BeginPlacement(HitObject hitObject)
|
||||
public void ShowPlacement(HitObject hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
public void EndPlacement(HitObject hitObject, bool commit)
|
||||
public void HidePlacement()
|
||||
{
|
||||
}
|
||||
|
||||
public void CommitPlacement(HitObject hitObject)
|
||||
{
|
||||
if (commit)
|
||||
AddHitObject(CreateHitObject(hitObject));
|
||||
|
||||
ResetPlacement();
|
||||
}
|
||||
|
||||
protected void ResetPlacement()
|
||||
@ -89,6 +89,10 @@ namespace osu.Game.Tests.Visual
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (CurrentBlueprint.PlacementActive == PlacementBlueprint.PlacementState.Finished)
|
||||
ResetPlacement();
|
||||
|
||||
updatePlacementTimeAndPosition();
|
||||
}
|
||||
|
||||
|
@ -152,7 +152,7 @@ namespace osu.Game.Users
|
||||
Margin = new MarginPadding { Top = main_content_height },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Left = 80, Vertical = padding },
|
||||
Padding = new MarginPadding(padding),
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
|
@ -20,7 +20,7 @@
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<PackageReference Include="DiffPlex" Version="1.7.2" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.67" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.70" />
|
||||
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||
<PackageReference Include="MessagePack" Version="2.5.187" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.10" />
|
||||
@ -35,9 +35,9 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="11.5.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.1025.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.1111.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1106.0" />
|
||||
<PackageReference Include="Sentry" Version="4.12.1" />
|
||||
<PackageReference Include="Sentry" Version="4.13.0" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
<PackageReference Include="SharpCompress" Version="0.38.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user